/* -*-C-*-
 ##############################################################################
 #
 # File:        trice/src/auto.c 
 # RCS:         "@(#)$Revision: 1.21 $ $Date: 94/03/09 10:26:13 $"
 # Description: User routines for autoranging and autozeroing E1430 module.
 # Author:      Doug Passey
 # Created:     
 # Language:    C
 # Package:     E1430
 # Status:      "@(#)$State: Exp $"
 #
 # (C) Copyright 1992, Hewlett-Packard Company, all rights reserved.
 #
 ##############################################################################
 #
 #
 # Revisions:
 #
 ##############################################################################
*/

#    include <stdio.h>
#    include <stdlib.h>

#include "trice.h"
#include "err1430.h"

#ifndef lint
const char auto_fileId[] = "$Header: auto.c,v 1.21 94/03/09 10:26:13 chriss Exp $";
#endif

#define AUTO_ZERO_BLOCKSIZE		2048
#define AUTO_ZERO_DECI_LEVEL		0
#define AUTO_ZERO_DAC_BITS		4096
#define AUTO_ZERO_LO_INTERP		3072
#define AUTO_ZERO_HI_INTERP		1024
#define AUTO_ZERO_INTERP_SPAN_BITS	AUTO_ZERO_LO_INTERP-AUTO_ZERO_HI_INTERP

#define AUTO_RANGE_FS			10000000.0
#define AUTO_RANGE_INIT_RANGE		0.0078125
#define AUTO_RANGE_MAX_RANGE		8.0
#define INPUT_RELAY_SETTLE_TIME		0.03


/*****************************************************************************
 *
 * Calculate settle time for relays and front end.
 *
 *****************************************************************************/
static SHORTSIZ16 i1430_get_settle_time(FLOATSIZ64 *time)
{
  FLOATSIZ64 clockTick;
  SHORTSIZ16 error;

  error = i1430_get_clock_tick(&clockTick);	/* the minimum time measureable */
  if(error) return(error);

  if(clockTick >= INPUT_RELAY_SETTLE_TIME) {
    *time = 2 * clockTick;
  }else{
    *time = INPUT_RELAY_SETTLE_TIME;
  }

  return(0);
}


/*****************************************************************************
 * Autorange all input modules in this groupID, checking input for <time>
 * seconds or until an overload occurs.  If an overload occurs, the range
 * is bumped up and is checked for another <time> seconds.
 *
 * If input = 0.0, estimate time to do autorange by the formula from 
 * Geoduck:
 *
 *		time = (decimation/Fs) * blocksize * 0.1.
 *
 * Returns negative error number if error, otherwise returns 0.
 ****************************************************************************/
SHORTSIZ16 e1430_auto_range(SHORTSIZ16 groupID, FLOATSIZ64 time) 
{
  aModGroupPtr ptr, firstPtr;
  SHORTSIZ16 *ok, i, numModules, la;
  SHORTSIZ16 status, done;
  FLOATSIZ64 *waittime;
  FLOATSIZ64 *startTime;
  SHORTSIZ16 error = 0;
  SHORTSIZ16 deci_bw;
  FLOATSIZ64 span;
  FLOATSIZ64 range;
  FLOATSIZ64 currentTime;
  LONGSIZ32 blocksize;
  char buf[80];
  FLOATSIZ64 settleTime;
  
  if((ptr = i1430_valid_module_group(groupID)) == NULL) {/* no such group exists */
    return (ERR1430_NO_GROUP);
  }

  firstPtr = ptr;

  numModules = i1430_number_of_modules(groupID);
  
  ok = (SHORTSIZ16 *)i1430_get_mem(numModules * sizeof(SHORTSIZ16));
  if(!ok) {
    error = i1430_Error(ERR1430_MALLOC, "autorange ok array", NULL);
    goto freestuff;
  }

  waittime = (FLOATSIZ64 *)i1430_get_mem(numModules * sizeof(FLOATSIZ64));
  if(!waittime) {
    error = i1430_Error(ERR1430_MALLOC, "autorange time array", NULL);
    goto freestuff;
  }
  
  startTime = (FLOATSIZ64 *)i1430_get_mem(numModules * sizeof(FLOATSIZ64));
  if(!startTime) {
    error = i1430_Error(ERR1430_MALLOC, "autorange startTime array", NULL);
    goto freestuff;
  }
  
  error = i1430_get_settle_time(&settleTime);
  if(error) goto freestuff;

  for(i=0, ptr = firstPtr; *ptr != -1; i++, ptr++) { /* init all arrays */
    la = e1430_modStates[*ptr].logicalAddr;		/* save typing */
    error = e1430_set_range_la(la, AUTO_RANGE_INIT_RANGE);
    if(error) goto freestuff;

    if(time == 0.0) {		/* calculate default time */
      deci_bw = i1430_get_decimation_bw_index(*ptr);
      blocksize = i1430_get_blocksize_index(*ptr);

      span = AUTO_RANGE_FS / (FLOATSIZ64)(1L << deci_bw); /* total span */
      waittime[i] = 0.1*(FLOATSIZ64)blocksize / span;
    }else{			/*  use supplied time */
      waittime[i] = time;
    }
    waittime[i] += settleTime;

    ok[i] = 0;				/* zero ok array */

    error = i1430_get_time(&(startTime[i]));		/* init startTime array */ 
    if(error) goto freestuff;
  }

  done = 0;
  while(!done) {
    done = 1;

    for(i=0, ptr = firstPtr; *ptr != -1; i++, ptr++) { 
      if(!ok[i]) {		/* if this module is not auto ranged yet */
        la = e1430_modStates[*ptr].logicalAddr;		/* save typing */
        error = i1430_get_time(&currentTime);
        if(error) goto freestuff;

	/* first wait for relay settling time */
        if(currentTime - startTime[i] > settleTime) {
          /* read status to clear any possible overloads during settling */
          error = e1430_get_status(la, &status);
          if(error) goto freestuff;
        }else{
	  done = 0;
	  continue;
        }
  
        error = e1430_get_status(la, &status);
        if(error) goto freestuff;
 
	if(status & E1430_MEAS_STATUS_OVERLOAD) {	/* if overload */

	  range = i1430_get_range_index(*ptr);
	  range *= 2.0;
	  if(range > AUTO_RANGE_MAX_RANGE) {
	    (void)sprintf(buf, "%d", (LONGSIZ32)la);
	    error = i1430_Error(ERR1430_AUTO_RANGE_MAX, buf, NULL);
	    goto freestuff;
	  }
	  error = e1430_set_range_la(la, range);
	  if(error) goto freestuff;

  	  error = i1430_get_time(&startTime[i]);
  	  if(error) goto freestuff;

	  done = 0;

	}else{				/* no overload */

          if(currentTime - startTime[i] >  waittime[i]) {
	    ok[i] = 1;			/* at correct range */
	  }else{			/* not waited long enough */
	    done = 0;
	  }	/* if not > waittime[i] */

	} 	/* if overload */

      }		/* if not ok */

    }	/* for all modules in group */

  }	/* while !done */

freestuff:
  if(waittime) i1430_free_mem(waittime);
  if(startTime) i1430_free_mem(startTime);
  if(ok) i1430_free_mem(ok);

  return (error);
}

/*****************************************************************************
 *
 * Autozero all input modules in this groupID.  The current state of each
 * module in the group is saved and each module in the group is set up to
 * make a measurement with the input of the ADC grounded.  The offset 
 * adjustment is strongly dependent on whether or not the input amplifier 
 * is switched into the circuit.  This input amplifier gets switched in
 * on the lower ranges.  To account for this effect zeroing is done for
 * two different ranges, once for a range that has the input amp in the
 * circuit and once for a range that does not have the amp in the circuit.
 *
 * The offsets calculated to zero the module at each range are stored in
 * the module images in the offsetLoRange and offsetHiRange fields; so
 * that whenever a range change occurs, the correct offset for the condition
 * of the input amplifier is automatically applied.
 *
 * Calculate the gain of the offset DAC by setting it a two widely separate
 * points and measuring the voltage with the input of the ADC grounded. This
 * measurement is done at the highest range in the high range group.  
 * Use linear interpolation to calculate the offset DAC's gain.
 *
 * Returns negative error number if error, otherwise returns 0.
 *
 ****************************************************************************/
SHORTSIZ16 e1430_auto_zero(SHORTSIZ16 groupID)
{
  aModuleImage *image;
  aModGroupPtr ptr, firstPtr;
  SHORTSIZ16 numModules;
  SHORTSIZ16 error;
  SHORTSIZ16 rangeIndex;
  FLOATSIZ64 settleTime;
  LONGSIZ32 *currentOffset;
  LONGSIZ32 *hiPoint;
  LONGSIZ32 loPoint;
  FLOATSIZ64 dacGain;
  SHORTSIZ16 *currentRange;
  LONGSIZ32 i, j;
  SHORTSIZ16 *data;
  SHORTSIZ16 offsetBits;
  FLOATSIZ64 floatBits;
  SHORTSIZ16 adcError;
  SHORTSIZ16 adcOverload;
  LONGSIZ32 actualCnt;
  SHORTSIZ16 la, masterLa, masterState;
  char buf[80];
  SHORTSIZ16 rangeBits;

  if((ptr = i1430_valid_module_group(groupID)) == NULL) {/* no such group exists */
    return (ERR1430_NO_GROUP);
  }
  firstPtr = ptr;

  numModules = i1430_number_of_modules(groupID);

  error = i1430_get_settle_time(&settleTime);
  if(error) goto freeit;

  data = (SHORTSIZ16 *)i1430_get_mem(AUTO_ZERO_BLOCKSIZE * sizeof(SHORTSIZ16));
  if(!data) {
    error = i1430_Error(ERR1430_MALLOC, "data array", NULL);
    goto freeit;
  }

  hiPoint = (LONGSIZ32 *)i1430_get_mem(numModules * sizeof(LONGSIZ32));
  if(!hiPoint) {
    error = i1430_Error(ERR1430_MALLOC, "autozero hiPoint array", NULL);
    goto freeit;
  }

  currentOffset= (LONGSIZ32 *)i1430_get_mem(numModules * sizeof(LONGSIZ32));
  if(!currentOffset) {
    error = i1430_Error(ERR1430_MALLOC, "autozero currentOffset array", NULL);
    goto freeit;
  }

  currentRange = (SHORTSIZ16 *)i1430_get_mem(numModules * sizeof(SHORTSIZ16));
  if(!currentRange) {
    error = i1430_Error(ERR1430_MALLOC, "autozero currentRange array", NULL);
    goto freeit;
  }

  image = (aModuleImage *)i1430_get_mem(numModules * sizeof(aModuleImage));
  if(!image) {
    error = i1430_Error(ERR1430_MALLOC, "autozero image array", NULL);
    goto freeit;
  }

  for(i=0; i < numModules; i++, ptr++) {	/* over all modules in group */
    image[i] = e1430_modStates[*ptr];		/* save current module state */
    for(j=0; j<RANGE_STEPS; j++) {		/* save current range index */
      i1430_get_range_bits_index((SHORTSIZ16)j, &rangeBits);
      if(rangeBits == 
		(e1430_modStates[*ptr].analogSetup & ~ANALOG_SETUP_RANGE_MASK)) {
	currentRange[i] = (SHORTSIZ16)j;	/*save index */
	break;
      }
    }
  }


  /* set up modules in group to measure offset voltage */
  /* insure there is a synchronous group if more than one module */
  masterLa = -1;
  if(numModules > 1) {	/* make sure multi-module group is synchronized */
    for(i=0, ptr = firstPtr; i < numModules; i++, ptr++) {/* all in group */
      error = e1430_get_clock_master_mode(e1430_modStates[*ptr].logicalAddr,
							&masterState);
      if(error) return(error);

      if(masterState == E1430_MASTER_CLOCK_ON) {
	masterLa = e1430_modStates[*ptr].logicalAddr;
        break;
      }
    }
  }

  if(masterLa == -1) {	 /* if no master, or only one module */
    masterLa = e1430_modStates[*firstPtr].logicalAddr;
  }

  /* insure all modules have internal clocks */
  error = e1430_write_register(groupID, E1430_TIMING_SETUP_REG, 0);
  if(error) return(error);

  /* put all E1430s into know state without hard reset to ADC */
  for(i=0, ptr=firstPtr; i<numModules; i++, ptr++) {
    la = e1430_modStates[*ptr].logicalAddr;
    (void)i1430_init_mod_state(la, &e1430_modStates[*ptr]);
    error = i1430_reset_E1430(la);
    if(error) return(error);
  }

  if(numModules > 1) {
    error = e1430_set_clock_master_mode(masterLa, E1430_MASTER_CLOCK_ON);
    if(error) return(error);
    error = e1430_set_multi_sync(groupID, E1430_MULTI_SYNC_ON);
    if(error) return(error);
  }

  error = e1430_set_input_low(groupID, E1430_INPUT_LO_FLOAT);
  if(error) goto freeit;
  error = e1430_set_input_high(groupID, E1430_INPUT_HI_GROUND); 
  if(error) goto freeit;

  error = e1430_set_center_frequency(groupID, 0.0);
  if(error) goto freeit;

  error = e1430_set_data_format(groupID, E1430_DATA_TYPE_REAL, 
		E1430_DATA_SIZE_16, E1430_BLOCK_MODE, 
		AUTO_ZERO_BLOCKSIZE, E1430_APPEND_STATUS_OFF);
  if(error) goto freeit;

  error = e1430_set_trigger_source(groupID, E1430_TRIGGER_SOURCE_AUTO);
  if(error) goto freeit;


  error = e1430_set_decimation_filter(groupID, AUTO_ZERO_DECI_LEVEL, 
			E1430_DECIMATION_OFF, E1430_ONEPASS, E1430_PASS_TAG_32);
  if(error) goto freeit;


  error = e1430_set_data_port(groupID, E1430_SEND_PORT_VME);
  if(error) goto freeit;


  for(rangeIndex=0; rangeIndex < RANGE_STEPS; rangeIndex++) {
    i1430_get_range_bits_index(rangeIndex, &rangeBits);
    error = i1430_update_group_module_bits(groupID, E1430_ANALOG_SETUP_REG, 
		ANALOG_SETUP_RANGE_MASK, rangeBits);
    if(error) goto freeit;
 
    /* get current offset */
    e1430_pause(settleTime);		/* wait for front end to settle */
  
    error = e1430_arm_module(groupID);
    if(error) goto freeit;

    if(e1430_device_clear_flag) goto freeit;	/* device clear issued in SCPI */

    /* calculate current offset */
    for(ptr = firstPtr, i=0; *ptr != -1; ptr++,i++) { 	/* measure hi point */
      error = e1430_read_raw_data(e1430_modStates[*ptr].logicalAddr, data, 
			2 * (LONGSIZ32)AUTO_ZERO_BLOCKSIZE, 
			&adcOverload, &adcError, &actualCnt);
      if(error) goto freeit;

      currentOffset[i] = 0;
      for(j=0; j<AUTO_ZERO_BLOCKSIZE; j++) {
        currentOffset[i] += (LONGSIZ32)data[j];
      }
      currentOffset[i] /= (LONGSIZ32)AUTO_ZERO_BLOCKSIZE;
    }
 
    /* get value for high interpolation point */
    error = i1430_update_group_module_bits(groupID, E1430_INPUT_OFFSET_REG, 
						  0, AUTO_ZERO_HI_INTERP); 
    if(error) goto freeit;
  
    e1430_pause(settleTime);		/* wait for front end to settle */
    
    error = e1430_arm_module(groupID);
    if(error) goto freeit;
  
    if(e1430_device_clear_flag) goto freeit;	/* device clear issued in SCPI */

    /* calculate input offset for offset DAC at first interpolation point */
    for(ptr = firstPtr, i=0; *ptr != -1; ptr++,i++) { 	/* measure hi point */
      error = e1430_read_raw_data(e1430_modStates[*ptr].logicalAddr, data, 
			2 * (LONGSIZ32)AUTO_ZERO_BLOCKSIZE, 
		    	&adcOverload, &adcError, &actualCnt);
      if(error) goto freeit;

      hiPoint[i] = 0;
      for(j=0; j<AUTO_ZERO_BLOCKSIZE; j++) {
        hiPoint[i] += (LONGSIZ32)data[j];
      }
      hiPoint[i] /= (LONGSIZ32)AUTO_ZERO_BLOCKSIZE;
    }
 
    /* set DAC at second interpolation point */
    error = i1430_update_group_module_bits(groupID, E1430_INPUT_OFFSET_REG, 
						  0, AUTO_ZERO_LO_INTERP); 
    if(error) goto freeit;
    
    e1430_pause(settleTime);
   
    error = e1430_arm_module(groupID);
    if(error) goto freeit;
  
    if(e1430_device_clear_flag) goto freeit;	/* device clear issued in SCPI */

    /* measure lo point, calculate gain and offset */
    for(ptr = firstPtr, i=0; *ptr != -1; ptr++,i++) {
      error = e1430_read_raw_data(e1430_modStates[*ptr].logicalAddr, data, 
			2 * (LONGSIZ32)AUTO_ZERO_BLOCKSIZE, 
		    	&adcOverload, &adcError, &actualCnt);
      if(error) goto freeit;
      loPoint = 0;
      for(j=0; j<AUTO_ZERO_BLOCKSIZE; j++) {
        loPoint += (LONGSIZ32)data[j];
      }
      loPoint /= (LONGSIZ32)AUTO_ZERO_BLOCKSIZE;
  
      if(hiPoint[i] - loPoint == 0L) { /* prevent floating point divide error */
	(void)sprintf(buf, "%d", (LONGSIZ32)e1430_modStates[*ptr].logicalAddr);
	return(i1430_Error(ERR1430_AUTO_ZERO_FAIL, buf, NULL));
      }

      dacGain = (FLOATSIZ64)(AUTO_ZERO_INTERP_SPAN_BITS) /
				(FLOATSIZ64)(hiPoint[i] - loPoint); 

      /* calculate bits to null input */
      floatBits = (FLOATSIZ64)currentOffset[i] * dacGain;

      offsetBits = (SHORTSIZ16)floatBits;	/* round, don't truncate */
      if(floatBits < 0.0) {
        if((SHORTSIZ16)(floatBits - .5) != offsetBits) {
	  offsetBits--;
        }
      }else{
        if((SHORTSIZ16)(floatBits + .5) != offsetBits) {
	  offsetBits++;
        }
      }

      image[i].offsetSave[rangeIndex] = 0x800 + offsetBits;

/*printf("hi = %ld, lo = %ld, current = %ld, offsetBits = %d, offset = %hX\n",
hiPoint[i], loPoint, currentOffset[i], offsetBits, image[i].offsetSave[rangeIndex]); */
    }
  
  } /* for rangeIndex < RANGE_STEPS */

  /* restore the previous states, changing only the offset registers */
  /* restore the master module first in case of synchronous group */
  for(i=0; i<numModules; i++) {
    if(image[i].logicalAddr == masterLa) break;
  }

  image[i].offset = image[i].offsetSave[currentRange[i]];
  error = i1430_restore_state_la(masterLa, &(image[i]));
  if(error) goto freeit;

  /* now restore rest of group, if any */
  for(ptr=firstPtr, i=0; *ptr != -1; ptr++,i++) {/* over all modules in group */
    if(image[i].logicalAddr != masterLa) {
      image[i].offset = image[i].offsetSave[currentRange[i]];
      error = i1430_restore_state_la(e1430_modStates[*ptr].logicalAddr, &(image[i]));
      if(error) goto freeit;
    }
  }

freeit:
  if(data) i1430_free_mem(data);
  if(image) i1430_free_mem(image);
  if(hiPoint) i1430_free_mem(hiPoint);
  if(currentOffset) i1430_free_mem(currentOffset);
  if(currentRange) i1430_free_mem(currentRange);

  e1430_pause(settleTime);  /* wait for front end to settle after restore */
  return (error);
}


